﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2012-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI
* GitHub: https://github.com/yhuse/SunnyUI
*
* SunnyUI.dll can be used for free under the MulanPSL2 license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: UComPort.cs
* 文件说明: 串口扩展类
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2022-11-01: V1.0.0 增加文件说明
******************************************************************************/

using Sunny.FrameDecoder;
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;

namespace Sunny.Com
{
    /// <summary>
    /// 串口扩展类
    /// </summary>
    public sealed class ComPort : IDisposable
    {
        private readonly SerialPort _comm;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="dataType">串口数据类型</param>
        /// <param name="portName">串口名称</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">奇偶校验</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        public ComPort(DataType dataType, string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
        {
            DataType = dataType;

            _comm = new SerialPort
            {
                PortName = portName,
                BaudRate = baudRate,
                Parity = parity,
                DataBits = dataBits,
                StopBits = stopBits,
            };

            _comm.DataReceived += SerialPortDataReceived;
            _comm.ErrorReceived += Comm_ErrorReceived;
            _comm.PinChanged += Comm_PinChanged;
        }

        private void Comm_PinChanged(object sender, SerialPinChangedEventArgs e)
        {
            PinChanged?.Invoke(this, e);
        }

        private void Comm_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            ErrorReceived?.Invoke(this, e);
        }

        /// <summary>
        /// 标签
        /// </summary>
        public object Tag { get; set; }

        private string name;

        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get => name ?? this.GetType().Name;
            set => name = value;
        }

        /// <summary>
        /// 指示通过串口对象表示的端口上发生了错误
        /// </summary>
        public event SerialErrorReceivedEventHandler ErrorReceived;

        /// <summary>
        /// 指示通过串口对象表示的端口上发生非数据信号事件
        /// </summary>
        public event SerialPinChangedEventHandler PinChanged;

        /// <summary>
        /// 输入缓冲区，必须为2的N次方
        /// </summary>
        public int ReadBufferSize
        {
            get => _comm.ReadBufferSize;
            set => _comm.ReadBufferSize = DecoderHelper.Get2PowNHigh(value);
        }

        /// <summary>
        /// 输出缓冲区，必须为2的N次方
        /// </summary>
        public int WriteBufferSize
        {
            get => _comm.WriteBufferSize;
            set => _comm.WriteBufferSize = DecoderHelper.Get2PowNHigh(value);
        }

        /// <summary>
        /// 串口数据类型
        /// </summary>
        public DataType DataType { get; }

        /// <summary>
        /// 串口
        /// </summary>
        public SerialPort Instance => _comm;

        /// <summary>
        /// 串口是否打开
        /// </summary>
        public bool IsOpen => _comm.IsOpen;

        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            if (!IsOpen)
            {
                return;
            }

            _comm.Close();
        }

        /// <summary>
        /// 打开串口
        /// </summary>
        public void Open()
        {
            Close();
            _comm.Open();
        }

        /// <summary>
        /// 打开串口
        /// </summary>
        /// <param name="portName">串口名称</param>
        public void ReOpen(string portName)
        {
            Close();
            _comm.PortName = portName;
            _comm.Open();
        }

        /// <summary>
        /// 打开串口
        /// </summary>
        /// <param name="portName">串口名称</param>
        public bool TryReOpen(string portName)
        {
            Close();
            _comm.PortName = portName;

            try
            {
                _comm.Open();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            return IsOpen;
        }

        /// <summary>
        /// 尝试打开串口
        /// </summary>
        /// <returns></returns>
        public bool TryOpen()
        {
            Close();

            try
            {
                _comm.Open();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            return IsOpen;
        }

        /// <summary>
        /// 休眠间隔
        /// </summary>
        public int ThreadSleepInterval { get; set; } = 100;

        private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            Thread.Sleep(ThreadSleepInterval);

            if (DataType == DataType.String)
            {
                if (frameDecoder != null && frameDecoder is BaseStringFrameDecoder stringFrameDecoder)
                    stringFrameDecoder.Decode(ReadCommString());
                else
                    OnReceive?.Invoke(this, null, new StringDataEventArgs(null, ReadCommString()));
            }

            if (DataType == DataType.Byte)
            {
                if (frameDecoder != null && frameDecoder is BaseByteFrameDecoder byteFrameDecoder)
                    byteFrameDecoder.Decode(ReadCommBytes());
                else
                    OnReceive?.Invoke(this, new ByteDataEventArgs(null, ReadCommBytes()), null);
            }
        }

        private void ByteFrameDecoder_OnDecoder(object sender, IByteEventArgs e)
        {
            OnReceive?.Invoke(this, e, null);
        }

        private void StringFrameDecoder_OnDecoder(object sender, IStringEventArgs e)
        {
            OnReceive?.Invoke(this, null, e);
        }

        private BaseFrameDecoder frameDecoder;

        /// <summary>
        /// 解码器
        /// </summary>
        public BaseFrameDecoder FrameDecoder
        {
            get => frameDecoder;
            set
            {
                frameDecoder = value;
                if (frameDecoder != null && frameDecoder is BaseByteFrameDecoder byteFrameDecoder)
                {
                    byteFrameDecoder.OnDecoder += ByteFrameDecoder_OnDecoder;
                }

                if (frameDecoder != null && frameDecoder is BaseStringFrameDecoder stringFrameDecoder)
                {
                    stringFrameDecoder.OnDecoder += StringFrameDecoder_OnDecoder;
                }
            }
        }

        /// <summary>
        /// 接收串口数据
        /// </summary>
        public event OnComReceive OnReceive;

        private string ReadCommString()
        {
            return _comm.ReadExisting();
        }

        private byte[] ReadCommBytes()
        {
            var bytesRead = _comm.BytesToRead;
            var dataBytes = new byte[bytesRead];
            //读取缓冲区数据
            _comm.Read(dataBytes, 0, bytesRead);
            return dataBytes;
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        public void Write(byte[] buffer)
        {
            Write(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="offset">偏移</param>
        /// <param name="count">长度</param>
        public void Write(byte[] buffer, int offset, int count)
        {
            if (!IsOpen)
            {
                return;
            }

            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException("Offset must be greater than or equal to zero.");
            }

            if (offset + count > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("count must be less than write position.");
            }

            _comm.Write(buffer, offset, count);
        }

        /// <summary>
        /// 发送命令
        /// </summary>
        /// <param name="cmdString">数据</param>
        /// <param name="addNewLine">是否增加换行字符串</param>
        public void Write(string cmdString, bool addNewLine = false)
        {
            if (!IsOpen)
            {
                return;
            }

            if (!addNewLine)
            {
                _comm.WriteLine(cmdString);
            }
            else
            {
                _comm.WriteLine(cmdString + Environment.NewLine);
            }
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        public void Dispose()
        {
            Close();
            _comm.Dispose();
        }

        /// <summary>
        /// 获取当前设备所有串口列表
        /// </summary>
        /// <returns>串口列表</returns>
        public static string[] GetPortNames()
        {
            return SerialPort.GetPortNames();
        }

        /// <summary>
        /// 获取发送缓冲区中数据的字节数
        /// </summary>
        public int BytesToWrite => _comm.BytesToWrite;

        /// <summary>
        /// 获取接收缓冲区中数据的字节数
        /// </summary>
        public int BytesToRead => _comm.BytesToRead;

        /// <summary>
        /// 获取或者设置中断信号状态
        /// </summary>
        public bool BreakState
        {
            get => _comm.BreakState;
            set => _comm.BreakState = value;
        }

        /// <summary>
        /// 获取端口的载波检测行的状态
        /// </summary>
        public bool CDHolding => _comm.CDHolding;

        /// <summary>
        /// 获取“可以发送”行的状态
        /// </summary>
        public bool CtsHolding => _comm.CtsHolding;

        /// <summary>
        /// 获取或者设置一个值，该值指示 null 字节在端口和接收缓冲区之间传输时是否被忽略
        /// </summary>
        public bool DiscardNull
        {
            get => _comm.DiscardNull;
            set => _comm.DiscardNull = value;
        }

        /// <summary>
        /// 获取数据设置就绪（DSR）信号的状态
        /// </summary>
        public bool DsrHolding => _comm.DsrHolding;

        /// <summary>
        /// 获取或设置一个值，该值在串行通信过程中启用数据终端就绪（DTR）信号
        /// </summary>
        public bool DtrEnable
        {
            get => _comm.DtrEnable;
            set => _comm.DtrEnable = value;
        }

        /// <summary>
        /// 使用 Handshake 中的值获取或者设置串行端口数据传输的握手协议
        /// </summary>
        public Handshake Handshake
        {
            get => _comm.Handshake;
            set => _comm.Handshake = value;
        }

        /// <summary>
        /// 获取或者设置用于解释读写行方法调用结束的值
        /// </summary>
        public string NewLine
        {
            get => _comm.NewLine;
            set => _comm.NewLine = value;
        }

        /// <summary>
        /// 获取或设置一个字节，该字节在发生奇偶校验错误时替换数据流中的无效字节
        /// </summary>
        public byte ParityReplace
        {
            get => _comm.ParityReplace;
            set => _comm.ParityReplace = value;
        }

        /// <summary>
        /// 获取或者设置读取操作未完成时发生超时之前的毫秒数
        /// </summary>
        public int ReadTimeout
        {
            get => _comm.ReadTimeout;
            set => _comm.ReadTimeout = value;
        }

        /// <summary>
        /// 获取或者设置 DataReceived 事件发生前内部输入缓冲区中的字节数
        /// </summary>
        public int ReceivedBytesThreshold
        {
            get => _comm.ReceivedBytesThreshold;
            set => _comm.ReceivedBytesThreshold = value;
        }

        /// <summary>
        ///  获取或设置一个值，该值指示在串行通信中是否启用请求发送（RTS）信号
        /// </summary>
        public bool RtsEnable
        {
            get => _comm.RtsEnable;
            set => _comm.RtsEnable = value;
        }

        /// <summary>
        /// 获取或者设置写入操作未完成时发生超时之前的毫秒数
        /// </summary>
        public int WriteTimeout
        {
            get => _comm.WriteTimeout;
            set => _comm.WriteTimeout = value;
        }

        /// <summary>
        /// 获取或者设置通信端口，包括但不限于所有可用的 COM 端口
        /// </summary>
        public string PortName
        {
            get => _comm.PortName;
            set => _comm.PortName = value;
        }

        /// <summary>
        /// 获取或者设置串行波特率
        /// </summary>
        public int BaudRate
        {
            get => _comm.BaudRate;
            set => _comm.BaudRate = value;
        }

        /// <summary>
        /// 获取或者设置每个字节的标准数据位长度
        /// </summary>
        public int DataBits
        {
            get => _comm.DataBits;
            set => _comm.DataBits = value;
        }

        /// <summary>
        /// 获取或者设置奇偶校验检查协议
        /// </summary>
        public Parity Parity
        {
            get => _comm.Parity;
            set => _comm.Parity = value;
        }

        /// <summary>
        /// 获取或者设置每个字节的标准停止位数
        /// </summary>
        public StopBits StopBits
        {
            get => _comm.StopBits;
            set => _comm.StopBits = value;
        }

        /// <summary>
        /// 丢弃来自串行驱动程序中接收缓冲区的数据
        /// </summary>
        public void DiscardInBuffer()
        {
            _comm.DiscardInBuffer();
        }

        /// <summary>
        /// 丢弃来自串行驱动程序中传输缓冲区的数据
        /// </summary>
        public void DiscardOutBuffer()
        {
            _comm.DiscardOutBuffer();
        }

        /// <summary>
        /// 获取或者设置传输前后文本转换的字节编码
        /// </summary>
        public Encoding Encoding
        {
            get => _comm.Encoding;
            set => _comm.Encoding = value;
        }
    }
}